/**
 *  @file BufferedReader.cpp
 */
#include "BufferedReader.h"
#include "../common/CppMemPool.h"
#include "../collections/CircularBuffer.h"
#include "../lang/IntCheck.h"
#include "../common/UtilFuncT.h"

using namespace cppflib::collections;
using namespace cppflib::exceptions;

namespace cppflib
{

namespace io
{

BufferedReader::BufferedReader(void)
{
   this->readPos = 0;
   this->pOrgReader = NULL;
   this->pCirBuf = NULL;
}

BufferedReader::BufferedReader(Reader *pReader, sz_t bufferSize)
{
   this->pOrgReader = pReader;
   cppmempool::Retain(this->pOrgReader);
   this->pCirBuf = new CircularBuffer(bufferSize * sizeof(wc_t)); // buffer size is aligned to size of wide chars
   cppmempool::Retain(this->pCirBuf);
   this->readPos = 0;
}

BufferedReader::BufferedReader(Reader *pReader)
{
   const u32_t DEFAULT_READ_BUFFER_SIZE = 256 * sizeof(wc_t); // buffer size is aligned to size of wide chars

   this->pOrgReader = pReader;
   cppmempool::Retain(this->pOrgReader);
   this->pCirBuf = new CircularBuffer(DEFAULT_READ_BUFFER_SIZE);
   cppmempool::Retain(this->pCirBuf);
   this->readPos = 0;
}

BufferedReader::~BufferedReader(void)
{
   Close();
}

void BufferedReader::Close()
{
   if (this->pOrgReader) {
      cppmempool::Release(this->pOrgReader);
      this->pOrgReader = NULL;
   }

   if (this->pCirBuf) {
      cppmempool::Release(this->pCirBuf);
      this->pCirBuf = NULL;
   }
}

int BufferedReader::Read(ArrayWChar &buffer)
{
   return Read(buffer, 0, static_cast<int>(buffer.GetSize()));
}

int BufferedReader::Read(ArrayWChar &buffer, int offset, int count)
{
   if (count < 0 || offset < 0 || offset >= static_cast<int>(buffer.GetSize()))
      return 0;

   // use the smaller one
   count = cppflib::Min<int>(count, static_cast<int>(buffer.GetSize() - offset));

   int totalRead = 0;
   if (readPos < pCirBuf->GetValidLength()) {
      // copy unread portion in internal buffer

      // char left in binbuf
      sz_t charRead = (pCirBuf->GetValidLength() - readPos) / sizeof(wc_t);
      // read the smaller number
      charRead = cppflib::Min<sz_t>(static_cast<sz_t>(count), charRead);
      u8_t * destPtr = reinterpret_cast<u8_t*>(buffer.GetRawPtr() + offset);
      pCirBuf->GetData(destPtr, (charRead * sizeof(wc_t)), readPos);
      
      // update
      count -= static_cast<int>(charRead);
      offset += static_cast<int>(charRead);
      totalRead += static_cast<int>(charRead);
      readPos += static_cast<int>(charRead * sizeof(wc_t));
   }

   // nothing is read or buffer not full and underlying stream can be read without blocking
   if (totalRead == 0 || (count > 0  && pOrgReader->CanReadNoBlocking() > 0)) {  
      int nrRead = pOrgReader->Read(buffer, offset, count);
      if (nrRead > 0) {
         // copy data back to the circular buffer
         const u8_t * srcPtr = reinterpret_cast<u8_t*>(buffer.GetRawPtr() + offset);
         pCirBuf->AppendData(srcPtr, nrRead * sizeof(wc_t));
         readPos = pCirBuf->GetValidLength();

         totalRead += nrRead;
      }
   }
 
   return totalRead;
}

int BufferedReader::Read()
{
   int ret = -1;
   if (readPos < pCirBuf->GetValidLength()) {
      wc_t tempChar;
      pCirBuf->GetData(reinterpret_cast<u8_t*>(&tempChar), sizeof(wc_t), readPos);
      readPos += sizeof(wc_t);
      ret = tempChar & 0xffff;
   }
   else {
      ret = pOrgReader->Read();
      if (ret >= 0) {
         wc_t data = static_cast<wc_t>(ret);
         pCirBuf->AppendData(reinterpret_cast<u8_t*>(&data), sizeof(wc_t));
         readPos = pCirBuf->GetValidLength();
      }
   }

   return ret;
}

i64_t BufferedReader::Skip(i64_t charCount)
{
   if (lang::CheckOverflow(lang::AOP_ADD, static_cast<i64_t>(readPos), (charCount * sizeof(wc_t))))
      return -1;

   i64_t pos = static_cast<i64_t>(readPos) + (charCount * sizeof(wc_t));
   if (pos < 0 || pos >= static_cast<i64_t>(pCirBuf->GetValidLength())) // can't move beyond buffer
      return -1;

   readPos = static_cast<u32_t>(pos);
   return (readPos / sizeof(wc_t));  // the return value is incorrect actually
}

sz_t BufferedReader::CanReadNoBlocking()
{
   return ((pCirBuf->GetValidLength() - readPos) / sizeof(wc_t)) + pOrgReader->CanReadNoBlocking();
}


} // end of namespace io

} // end of namespace cppflib
